package com.faxsun.admin.web.controller.entity;

import com.faxsun.admin.web.domain.form.MainActions;
import com.faxsun.tools.DateHelper;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.broadleafcommerce.admin.web.controller.entity.AdminProductController;
import org.broadleafcommerce.common.exception.SecurityServiceException;
import org.broadleafcommerce.common.exception.ServiceException;
import org.broadleafcommerce.common.util.BLCSystemProperty;
import org.broadleafcommerce.openadmin.dto.*;
import org.broadleafcommerce.openadmin.server.domain.PersistencePackageRequest;
import org.broadleafcommerce.openadmin.server.security.remote.EntityOperationType;
import org.broadleafcommerce.openadmin.server.service.persistence.PersistenceResponse;
import org.broadleafcommerce.openadmin.web.form.component.ListGrid;
import org.broadleafcommerce.openadmin.web.form.entity.EntityForm;
import org.broadleafcommerce.openadmin.web.form.entity.EntityFormAction;
import org.broadleafcommerce.openadmin.web.form.entity.Field;
import org.joda.time.DateTime;
import org.springframework.ui.Model;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

public class ProductAdminController extends AdminProductController {

    @Override
    protected String getSectionKey(Map<String, String> pathVars) {
        // allow external links to work for ToOne items
        if (super.getSectionKey(pathVars) != null) {
            return super.getSectionKey(pathVars);
        }
        return SECTION_KEY;
    }

    /**
     * 查看产品列表，添加分页功能
     */
    @Override
    public String viewEntityList(HttpServletRequest request,
                                 HttpServletResponse response, Model model,
                                 @PathVariable Map<String, String> pathVars,
                                 @RequestParam MultiValueMap<String, String> requestParams)
            throws Exception {

        String sectionKey = getSectionKey(pathVars);
        String sectionClassName = getClassNameForSection(sectionKey);
        List<SectionCrumb> crumbs = getSectionCrumbs(request, null, null);
        PersistencePackageRequest ppr = getSectionPersistencePackageRequest(
                sectionClassName, requestParams, crumbs, pathVars);
        ClassMetadata cmd = service.getClassMetadata(ppr).getDynamicResultSet()
                .getClassMetaData();
        String view = super.viewEntityList(request, response, model, pathVars,
                requestParams);
        Map<String, Object> map = model.asMap();
        List<EntityFormAction> mainActions = (List<EntityFormAction>) map
                .get("mainActions");
        model.addAttribute("checkbox", "true");
        this.addUpdateActionIfAllowed(sectionClassName, cmd, mainActions);
        return view;

    }

    @Override
    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public String viewEntityForm(HttpServletRequest request,
                                 HttpServletResponse response, Model model,
                                 @PathVariable Map<String, String> pathVars,
                                 @PathVariable(value = "id") String id) throws Exception {
        String view = super.viewEntityForm(request, response, model, pathVars,
                id);

        EntityForm form = (EntityForm) model.asMap().get("entityForm");
        form.addAction(new EntityFormAction("CopyAProduct")
                .withButtonClass("copy-a-product")
                .withUrlPostfix("/" + id + "/copy").withIconClass("icon-copy")
                .withDisplayText("Copy_A_Product"));

        return view;
    }

    @RequestMapping(value = "/updates", method = RequestMethod.GET)
    public String batchUpdateActiveDate(HttpServletRequest request,
                                        HttpServletResponse response, Model model,
                                        @PathVariable Map<String, String> pathVars,
                                        @RequestParam MultiValueMap<String, String> requestParams)
            throws UnsupportedEncodingException {
        HttpSession session = request.getSession();
        String refer = request.getHeader("Referer");
        String token = (String) session.getAttribute("csrfToken");
        String sectionKey = getSectionKey(pathVars);
        model.addAttribute("viewType", "modal/batchShelves");
        model.addAttribute("currentUrl", request.getRequestURL().toString());
        setModelAttributes(model, sectionKey);
        model.addAttribute("modalHeaderType", "custom");
        model.addAttribute("modalTitle", "update_active_date");
        model.addAttribute("csrfToken", token);
        // model.addAttribute("query", this.parseSearchString(refer));
        // ExploitProtectionService

        return "modules/modalContainer";
    }

    @RequestMapping(value = "/updates/execute", method = RequestMethod.POST)
    public String executeBatchUpdateActiveDate(HttpServletRequest request,
                                               HttpServletResponse response, Model model,
                                               @PathVariable Map<String, String> pathVars,
                                               @RequestParam MultiValueMap<String, String> requestParams)
            throws Exception {
        String sectionKey = getSectionKey(pathVars);
        String sectionClassName = getClassNameForSection(sectionKey);
        List<SectionCrumb> crumbs = getSectionCrumbs(request, null, null);
        String applyAll = null;
        String maxIndex = null;
        String productIds = null;
        Entity[] entitries = null;
        String activeStartDate = null;
        String activeEndDate = null;
        if (requestParams.containsKey("applyAll")) {
            applyAll = requestParams.get("applyAll").get(0);
        }
        if (requestParams.containsKey("maxIndex")) {
            maxIndex = requestParams.get("maxIndex").get(0);
        }
        if (requestParams.containsKey("productIds")) {
            productIds = requestParams.get("productIds").get(0);
        }
        activeStartDate = requestParams.get("activeStartDate").get(0);
        activeEndDate = requestParams.get("activeEndDate").get(0);
        if (applyAll != null && applyAll.equals("on")) {
            FilterAndSortCriteria[] fascs = getCriteria(parseRequestParam(requestParams
                    .get("query").get(0)));
            PersistencePackageRequest ppr = getSectionPersistencePackageRequest(
                    sectionClassName, crumbs, pathVars);
            if (fascs != null) {
                ppr.setFilterAndSortCriteria(fascs);
            }
            int max = Integer.parseInt(maxIndex);
            for (int start = 0; start <= max; start += 50) {
                ppr.setStartIndex(start);
                int end = start + 49;
                if (end > max) {
                    end = max;
                }
                ppr.setMaxIndex(end);
                DynamicResultSet drs = service.getRecords(ppr)
                        .getDynamicResultSet();
                entitries = (Entity[]) ArrayUtils.addAll(entitries,
                        drs.getRecords());
            }
        } else {
            PersistencePackageRequest ppr = getSectionPersistencePackageRequest(
                    sectionClassName, crumbs, pathVars);
            ClassMetadata cmd = service.getClassMetadata(ppr)
                    .getDynamicResultSet().getClassMetaData();
            if (productIds != null) {
                String[] ids = productIds.split(",");
                entitries = new Entity[ids.length];
                for (int i = 0; i < ids.length; i++) {
                    Entity entity = service.getRecord(ppr, ids[i], cmd, false)
                            .getDynamicResultSet().getRecords()[0];
                    entitries[i] = entity;
                }
            }
        }
        this.update(entitries, activeStartDate, activeEndDate);
        return "ajaxredirect:" + this.getContextPath(request) + sectionKey + "/";

    }

    @Override
    @RequestMapping(value = "/{id}/{collectionField:.*}/{collectionItemId}", method = RequestMethod.POST)
    public String updateCollectionItem(HttpServletRequest request,
                                       HttpServletResponse response, Model model,
                                       @PathVariable Map<String, String> pathVars,
                                       @PathVariable(value = "id") String id,
                                       @PathVariable(value = "collectionField") String collectionField,
                                       @PathVariable(value = "collectionItemId") String collectionItemId,
                                       @ModelAttribute(value = "entityForm") EntityForm entityForm,
                                       BindingResult result) throws Exception {
        String sectionKey = getSectionKey(pathVars);
        String mainClassName = getClassNameForSection(sectionKey);
        List<SectionCrumb> sectionCrumbs = getSectionCrumbs(request,
                sectionKey, id);
        ClassMetadata mainMetadata = service
                .getClassMetadata(
                        getSectionPersistencePackageRequest(mainClassName,
                                sectionCrumbs, pathVars)).getDynamicResultSet()
                .getClassMetaData();
        Property collectionProperty = mainMetadata.getPMap().get(
                collectionField);

        PersistencePackageRequest ppr = getSectionPersistencePackageRequest(
                mainClassName, sectionCrumbs, pathVars);
        Entity entity = service.getRecord(ppr, id, mainMetadata, false)
                .getDynamicResultSet().getRecords()[0];

        // TODO: Temp fix for the sequence BigDecimal -> Integer conversion
        // error
        Field sequenceField = entityForm.getFields().get("sequence");
        int sequenceInt = new BigDecimal(sequenceField.getValue()).intValue();
        sequenceField.setValue(String.valueOf(sequenceInt));

        // First, we must save the collection entity
        PersistenceResponse persistenceResponse = service
                .updateSubCollectionEntity(entityForm, mainMetadata,
                        collectionProperty, entity, collectionItemId,
                        sectionCrumbs);
        Entity savedEntity = persistenceResponse.getEntity();
        entityFormValidator.validate(entityForm, savedEntity, result);

        if (result.hasErrors()) {
            return showViewUpdateCollection(request, model, pathVars, id,
                    collectionField, collectionItemId, "updateCollectionItem",
                    entityForm, savedEntity);
        }

        // Next, we must get the new list grid that represents this collection
        ListGrid listGrid = getCollectionListGrid(mainMetadata, entity,
                collectionProperty, null, sectionKey, persistenceResponse,
                sectionCrumbs);
        model.addAttribute("listGrid", listGrid);

        // We return the new list grid so that it can replace the currently
        // visible one
        setModelAttributes(model, sectionKey);
        return "views/standaloneListGrid";
    }

    @RequestMapping(value = "/{id}/copy", method = RequestMethod.GET)
    public String copyEntity(HttpServletRequest request,
                             HttpServletResponse response, Model model,
                             @PathVariable Map<String, String> pathVars,
                             @PathVariable("id") String id, RedirectAttributes redirectAttributes)
            throws Exception {
        // 1. Fetch the entity data from an existing product
        String sectionKey = getSectionKey(pathVars);
        String sectionClassName = getClassNameForSection(sectionKey);
        List<SectionCrumb> sectionCrumbs = getSectionCrumbs(request, null, null);
        PersistencePackageRequest ppr = getSectionPersistencePackageRequest(
                sectionClassName, sectionCrumbs, pathVars);
        ClassMetadata cmd = service.getClassMetadata(ppr).getDynamicResultSet()
                .getClassMetaData();
        Entity currentEntity = service.getRecord(ppr, id, cmd, false)
                .getDynamicResultSet().getRecords()[0];
        Map<String, DynamicResultSet> subRecordsMap = service
                .getRecordsForAllSubCollections(ppr, currentEntity,
                        sectionCrumbs);

        Integer upSaleProductCount = subRecordsMap.get("upSaleProducts")
                .getTotalRecords();
        modifyEntityProperties(currentEntity, upSaleProductCount);

        EntityForm productEntityForm = formService.createEntityForm(cmd,
                currentEntity, subRecordsMap, sectionCrumbs);

        setEntityActiveStartDate(productEntityForm);

        Entity productEntity = service.addEntity(productEntityForm,
                getSectionCustomCriteria(), sectionCrumbs).getEntity();

        final String productId = productEntity.getPMap().get("id").getValue();

        for (Entry<String, DynamicResultSet> subRecord : subRecordsMap
                .entrySet()) {
            if (subRecord.getValue().getTotalRecords().intValue() == 0
                    && !subRecord.getKey().equals("upSaleProducts")) {
                continue;
            }

            final String collectionField = subRecord.getKey();
            Property collectionProperty = cmd.getPMap().get(collectionField);
            FieldMetadata md = collectionProperty.getMetadata();
            List<SectionCrumb> subSectionCrumbs = getSectionCrumbs(request,
                    sectionKey, productId);
            PersistencePackageRequest subPpr = PersistencePackageRequest
                    .fromMetadata(md, subSectionCrumbs);
            ClassMetadata collectionMetadata = service.getClassMetadata(subPpr)
                    .getDynamicResultSet().getClassMetaData();

            EntityForm subEntityForm = null;
            if (md instanceof AdornedTargetCollectionMetadata) {
                AdornedTargetCollectionMetadata fmd = (AdornedTargetCollectionMetadata) md;
                subEntityForm = formService.buildAdornedListForm(fmd,
                        subPpr.getAdornedList(), productId);

                Entity[] entityArray;
                if (subRecord.getKey().equals("upSaleProducts")) {
                    // If it's upSaleProducts, link the productEntity and
                    // currentEntity both way.
                    entityArray = new Entity[subRecord.getValue().getRecords().length + 1];
                    // Copy the Entity[] from subRecord to the destinate
                    // entityArray
                    System.arraycopy(subRecord.getValue().getRecords(), 0,
                            entityArray, 0,
                            subRecord.getValue().getRecords().length);
                    // Insert the currentEntity to at the end of the entity
                    // array.
                    entityArray[subRecord.getValue().getRecords().length] = currentEntity;
                    EntityForm currentEntityForm = formService
                            .buildAdornedListForm(fmd, subPpr.getAdornedList(),
                                    id);

                    // Persist the productEntity upSaleProduct data to the
                    // currentEntity
                    setAdornedListFormFromEntity(currentEntityForm,
                            productEntity, subPpr);
                    service.addSubCollectionEntity(currentEntityForm, cmd,
                            collectionProperty, currentEntity, sectionCrumbs);

                } else {
                    entityArray = subRecord.getValue().getRecords();
                }

                // Persist all entities to new productEntity, including the
                // currentEntity
                for (Entity entity : entityArray) {
                    setAdornedListFormFromEntity(subEntityForm, entity, subPpr);
                    service.addSubCollectionEntity(subEntityForm, cmd,
                            collectionProperty, productEntity, sectionCrumbs);
                }

            } else if (md instanceof MapMetadata) {
                MapMetadata fmd = (MapMetadata) md;
                subEntityForm = formService
                        .buildMapForm(fmd, subPpr.getMapStructure(),
                                collectionMetadata, productId);

                for (Entity entity : subRecord.getValue().getRecords()) {
                    for (Entry<String, Field> entry : subEntityForm.getFields()
                            .entrySet()) {
                        Property property = entity.findProperty(entry.getKey());
                        if (property != null)
                            entry.getValue().setValue(property.getValue());
                    }

                    service.addSubCollectionEntity(subEntityForm, cmd,
                            collectionProperty, productEntity, sectionCrumbs);
                }
            }
        }

        redirectAttributes.addFlashAttribute("headerFlash", "copy.successful");
        return "ajaxredirect:" + getContextPath(request) + sectionKey + "/"
                + productEntity.getPMap().get("id").getValue();
    }

    private static void setEntityActiveStartDate(EntityForm productEntityForm)
            throws ParseException {
        DateTime dateTime = new DateTime().plusHours(BLCSystemProperty
                .resolveIntSystemProperty("activateAfterCopied.Hours"));
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
        String activeStartDate = sdf.format(dateTime.toDate());

        productEntityForm.findField("defaultSku.activeStartDate").setValue(
                activeStartDate);
    }

    private static void setAdornedListFormFromEntity(
            EntityForm currentEntityForm, Entity productEntity,
            PersistencePackageRequest subPpr) {
        for (Entry<String, Field> entry : currentEntityForm.getFields()
                .entrySet()) {
            if (entry.getKey().equals("product.id")) {
                continue;
            } else if (entry.getKey().equals(
                    subPpr.getAdornedList().getTargetObjectPath() + "."
                            + subPpr.getAdornedList().getTargetIdProperty())) {
                entry.getValue().setValue(
                        productEntity.findProperty("id").getValue());
            } else {
                Property property = productEntity.findProperty(entry.getKey());
                if (property != null) {
                    entry.getValue().setValue(property.getValue());
                }
            }
        }
    }

    private static void modifyEntityProperties(Entity entity, Integer count) {
        entity.findProperty("defaultSku.skuMedia---primary").setValue(null);
        // entity.findProperty("id").setValue(null);

        String url = entity.findProperty("url").getValue();
        count++;

        if (url.length() > 0) {
            String strToAppend = "";
            int lastDashIndex = url.lastIndexOf("-");
            if (lastDashIndex > 0) {
                // give the URL containing a dash "-"
                if (lastDashIndex < url.length()) {
                    // given the case URL = xxx-xx..
                    String charNextToDash = url.substring(lastDashIndex + 1,
                            lastDashIndex + 2);
                    try {
                        Integer lastCharToInt = Integer
                                .parseInt(charNextToDash);
                        // i.e. URL = xxx-N (N is an integer)
                        lastCharToInt++;
                        url = url.substring(0, url.length() - 1)
                                + lastCharToInt.toString();
                    } catch (NumberFormatException nfe) {
                        strToAppend = "-" + count.toString();
                    }
                } else {
                    // given the case URL = xxx- (end with "-")
                    strToAppend = count.toString();
                }

            } else {
                strToAppend = "-" + count.toString();
            }

            if (!StringUtils.isEmpty(strToAppend)) {
                entity.findProperty("url").setValue(url + strToAppend);
            }
        }
    }

    private String getFilterAndSortQueryParam(
            MultiValueMap<String, String> requestParams) {
        if (requestParams == null || requestParams.size() == 0) {
            return null;
        }
        String filter = requestParams.get("query").get(0);
        return StringUtils.strip(filter);
    }

    /**
     * 添加批量上架按钮
     *
     * @param sectionClassName
     * @param cmd
     * @param mainActions
     */
    protected void addUpdateActionIfAllowed(String sectionClassName,
                                            ClassMetadata cmd, List<EntityFormAction> mainActions) {
        if (isBatchUpdateActionAllowed(sectionClassName, cmd)) {
            mainActions.add(MainActions.BATCH_UPDATE);
        }

        mainEntityActionsExtensionManager.getProxy().modifyMainActions(cmd,
                mainActions);
    }

    /**
     * 检查用户是否具有批量上下架的权限，需要配置基础数据 BATCH_UPDATE
     *
     * @param sectionClassName
     * @param cmd
     * @return
     */
    protected boolean isBatchUpdateActionAllowed(String sectionClassName,
                                                 ClassMetadata cmd) {
        boolean canUpdate = true;
        try {
            adminRemoteSecurityService.securityCheck(sectionClassName,
                    EntityOperationType.REMOVE);/**
             由于broadleaf中的EntityOperationType无法扩展，因此用(REMOVE)即PermissionType.DELETE类型代表批量操作。
             */
        } catch (ServiceException e) {
            if (e instanceof SecurityServiceException) {
                canUpdate = false;
            }
        }

        if (canUpdate) {
            checkReadOnly:
            {
                // check if all the metadata is read only
                for (Property property : cmd.getProperties()) {
                    if (property.getMetadata() instanceof BasicFieldMetadata) {
                        if (((BasicFieldMetadata) property.getMetadata())
                                .getReadOnly() == null
                                || !((BasicFieldMetadata) property
                                .getMetadata()).getReadOnly()) {
                            break checkReadOnly;
                        }
                    }
                }
                canUpdate = false;
            }
        }

        return canUpdate;
    }

    protected void update(Entity[] entities, String activeStartDate,
                          String activeEndDate) throws Exception {
        if (entities == null)
            return;
        for (Entity entity : entities) {
            Property activeStartDateProp = entity
                    .findProperty("defaultSku.activeStartDate");
            Property activeEndDateProp = entity
                    .findProperty("defaultSku.activeEndDate");
            activeStartDateProp.setValue(DateHelper.transform(activeStartDate,
                    null, null));
            activeEndDateProp.setValue(DateHelper.transform(activeEndDate,
                    null, null));
            PersistencePackageRequest ppr = PersistencePackageRequest
                    .standard()
                    .withEntity(entity)
                    .withCeilingEntityClassname(
                            "org.broadleafcommerce.core.catalog.domain.Product");
            service.update(ppr);
        }
    }

    private String parseSearchString(String url) {
        int idx = url.indexOf("?");
        if (idx > 0) {
            return StringUtils.substring(url, idx + 1);
        }
        return null;
    }

    private MultiValueMap<String, String> parseRequestParam(String search) {
        MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
        if (search == null || search.length() == 0) {
            return map;
        }
        String[] args = search.split("&");
        for (String arg : args) {
            String[] kvp = arg.split("=");
            map.add(StringUtils.strip(kvp[0]), StringUtils.strip(kvp[1]));
        }
        map.remove("startIndex");
        map.remove("maxIndex");
        return map;
    }

}
